package com.flame.shapeville.service;

import com.flame.shapeville.bean.Exercise;
import com.flame.shapeville.bean.Task;
import com.flame.shapeville.util.DbConnector;

import java.text.DecimalFormat;
import java.util.*;

/**
 * Service class responsible for managing exercises.
 * <p>
 * Handles exercise initialization, querying from database, sorting, and saving.
 *
 * @author Flame Dev Team
 * @version 1.0
 * @since 2025-05-05
 */
public class ExerciseService {
    /**
     * Database connector for Exercise data.
     */
    private final DbConnector<Exercise> exerciseDAO;

    /**
     * Database connector for Task data.
     */
    private final DbConnector<Task> taskDAO;

    /**
     * Database connector for ExerciseBank data used for generating new exercises.
     */
    private final DbConnector<Object> exerciseBankDAO;

    /**
     * Constant value for Pi used in calculations.
     */
    private final double PI = 3.14;

    /**
     * Constructs a new ExerciseService instance.
     * Initializes database connectors for Exercise, Task, and ExerciseBank.
     */
    public ExerciseService() {
        taskDAO = new DbConnector<>("db/Task.yml", Task.class);
        exerciseDAO = new DbConnector<>("db/Exercise.yml", Exercise.class);
        exerciseBankDAO = new DbConnector<>("db/ExerciseBank.yml", Object.class);
    }

    /**
     * Loads all exercises for a given task.
     * If none exist, generates new ones and saves them to the database.
     *
     * @param taskId ID of the task
     * @return List of Exercise objects
     */
    public List<Exercise> loadExercise(String taskId) {
        List<Exercise> exercises = exerciseDAO.query("taskId", taskId);
        if (exercises == null || exercises.isEmpty()) {
            exercises = initExercise(taskId);
            saveExercises(exercises);
        }
        exercises.sort(Comparator.comparing(Exercise::getAdvanced).thenComparing(Exercise::getNo));
        return exercises;
    }

    /**
     * Initializes exercises based on the task style.
     *
     * @param taskId ID of the task
     * @return List of newly generated Exercise objects
     */
    private List<Exercise> initExercise(String taskId) {
        Task task = taskDAO.querySingleton(taskId);
        return switch (task.getStyle()) {
            case TASK1 -> initTask1(taskId);
            case TASK2 -> initTask2(taskId);
            case TASK3 -> initTask3(taskId);
            case TASK4 -> initTask4(taskId);
            case BONUS1 -> initBonus1(taskId);
            case BONUS2 -> initBonus2(taskId);
        };
    }

    /**
     * Generates exercises for Task1.
     *
     * @param taskId ID of the task
     * @return List of Task1 exercises
     */
    private List<Exercise> initTask1(String taskId) {
        List<Exercise> exercises = new ArrayList<>();
        Map task1Bank = (Map) exerciseBankDAO.querySingleton("Task1");
        List<String> basicBank = (List<String>) task1Bank.get("basic");
        List<String> advancedBank = (List<String>) task1Bank.get("advanced");
        Collections.shuffle(basicBank);
        Collections.shuffle(advancedBank);
        String lastKey = exerciseDAO.getLastKey();
        if (lastKey == null) {
            lastKey = "0";
        }
        int id = Integer.parseInt(lastKey);
        for (int i = 0; i < 4; i++) {
            id++;
            exercises.add(new Exercise(String.valueOf(id), taskId, false, i + 1, basicBank.get(i)));
        }
        for (int i = 0; i < 4; i++) {
            id++;
            exercises.add(new Exercise(String.valueOf(id), taskId, true, i + 1, advancedBank.get(i)));
        }
        return exercises;
    }

    /**
     * Generates exercises for Task2.
     *
     * @param taskId ID of the task
     * @return List of Task2 exercises
     */
    private List<Exercise> initTask2(String taskId) {
        List<Exercise> exercises = new ArrayList<>();
        String lastKey = exerciseDAO.getLastKey();
        if (lastKey == null) {
            lastKey = "0";
        }
        int id = Integer.parseInt(lastKey);
        for (int i = 0; i < 5; i++) {
            id++;
            exercises.add(new Exercise(String.valueOf(id), taskId, false, i + 1));
        }
        return exercises;
    }

    /**
     * Generates exercises for Task3.
     *
     * @param taskId ID of the task
     * @return List of Task3 exercises
     */
    private List<Exercise> initTask3(String taskId) {
        List<Exercise> exercises = new ArrayList<>();
        String lastKey = exerciseDAO.getLastKey();
        if (lastKey == null) {
            lastKey = "0";
        }
        int id = Integer.parseInt(lastKey);
        exercises.add(task3Rectangle(++id, taskId));
        exercises.add(task3Parallelogram(++id, taskId));
        exercises.add(task3Triangle(++id, taskId));
        exercises.add(task3Trapezium(++id, taskId));
        return exercises;
    }

    /**
     * Generates a rectangle calculation exercise for Task3.
     */
    private Exercise task3Rectangle(Integer exerciseId, String taskId) {
        String id = String.valueOf(exerciseId);
        Exercise exercise = new Exercise(id, taskId, false, Integer.MAX_VALUE, 3 * 60);
        Random random = new Random();
        int width = random.nextInt(20) + 1;
        int height = random.nextInt(20) + 1;
        int answer = width * height;
        exercise.addParam("width", width);
        exercise.addParam("height", height);
        exercise.addParam("selected", false);
        exercise.addParam("shape", "rectangle");
        exercise.setAnswer(String.valueOf(answer));
        return exercise;
    }

    /**
     * Generates a parallelogram calculation exercise for Task3.
     */
    private Exercise task3Parallelogram(Integer exerciseId, String taskId) {
        String id = String.valueOf(exerciseId);
        Exercise exercise = new Exercise(id, taskId, false, Integer.MAX_VALUE, 3 * 60);
        Random random = new Random();
        int base = random.nextInt(20) + 1;
        int height = random.nextInt(20) + 1;
        int answer = base * height;
        exercise.addParam("base", base);
        exercise.addParam("height", height);
        exercise.addParam("selected", false);
        exercise.addParam("shape", "parallelogram");
        exercise.setAnswer(String.valueOf(answer));
        return exercise;
    }

    /**
     * Generates a triangle calculation exercise for Task3.
     */
    private Exercise task3Triangle(Integer exerciseId, String taskId) {
        String id = String.valueOf(exerciseId);
        Exercise exercise = new Exercise(id, taskId, false, Integer.MAX_VALUE, 3 * 60);
        Random random = new Random();
        int base = random.nextInt(20) + 1;
        int height = random.nextInt(20) + 1;
        double answer = base * height / 2.0;
        exercise.addParam("base", base);
        exercise.addParam("height", height);
        exercise.addParam("selected", false);
        exercise.addParam("shape", "triangle");
        exercise.setAnswer(String.valueOf(answer));
        return exercise;
    }

    /**
     * Generates a trapezium calculation exercise for Task3.
     */
    private Exercise task3Trapezium(Integer exerciseId, String taskId) {
        String id = String.valueOf(exerciseId);
        Exercise exercise = new Exercise(id, taskId, false, Integer.MAX_VALUE, 3 * 60);
        Random random = new Random();
        int top = random.nextInt(19) + 1;
        int base;
        do {
            base = random.nextInt(19) + 2;
        } while (base <= top);
        int height = random.nextInt(20) + 1;
        double answer = (base + top) * height / 2.0;
        exercise.addParam("base", base);
        exercise.addParam("top", top);
        exercise.addParam("height", height);
        exercise.addParam("selected", false);
        exercise.addParam("shape", "trapezium");
        exercise.setAnswer(String.valueOf(answer));
        return exercise;
    }

    /**
     * Generates exercises for Task4.
     */
    private List<Exercise> initTask4(String taskId) {
        List<Exercise> exercises = new ArrayList<>();
        String lastKey = exerciseDAO.getLastKey();
        if (lastKey == null) {
            lastKey = "0";
        }
        int id = Integer.parseInt(lastKey);

        exercises.add(task4RC(++id, taskId));
        exercises.add(task4RA(++id, taskId));
        exercises.add(task4DC(++id, taskId));
        exercises.add(task4DA(++id, taskId));

        return exercises;
    }

    /**
     * Generates a circle circumference exercise using radius.
     */
    private Exercise task4RC(Integer exerciseId, String taskId) {
        String id = String.valueOf(exerciseId);
        Exercise exercise = new Exercise(id, taskId, false, Integer.MAX_VALUE, 3 * 60);
        Random random = new Random();
        int radio = random.nextInt(20) + 1;
        double circumference = 2 * PI * radio;
        String answer = new DecimalFormat("#.0").format(circumference);
        exercise.addParam("radius", radio);
        exercise.addParam("selected", false);
        exercise.addParam("type", "R-C");
        exercise.setAnswer(answer);
        return exercise;
    }

    /**
     * Generates a circle area exercise using radius.
     */
    private Exercise task4RA(Integer exerciseId, String taskId) {
        String id = String.valueOf(exerciseId);
        Exercise exercise = new Exercise(id, taskId, false, Integer.MAX_VALUE, 3 * 60);
        Random random = new Random();
        int radio = random.nextInt(20) + 1;
        double area = PI * radio * radio;
        String answer = new DecimalFormat("#.0").format(area);
        exercise.addParam("radius", radio);
        exercise.addParam("selected", false);
        exercise.addParam("type", "R-A");
        exercise.setAnswer(answer);
        return exercise;
    }

    /**
     * Generates a circle circumference exercise using diameter.
     */
    private Exercise task4DC(Integer exerciseId, String taskId) {
        String id = String.valueOf(exerciseId);
        Exercise exercise = new Exercise(id, taskId, false, Integer.MAX_VALUE, 3 * 60);
        Random random = new Random();
        int diagonal = random.nextInt(20) + 1;
        double circumference = PI * diagonal;
        String answer = new DecimalFormat("#.0").format(circumference);
        exercise.addParam("diameter", diagonal);
        exercise.addParam("selected", false);
        exercise.addParam("type", "D-C");
        exercise.setAnswer(answer);
        return exercise;
    }

    /**
     * Generates a circle area exercise using diameter.
     */
    private Exercise task4DA(Integer exerciseId, String taskId) {
        String id = String.valueOf(exerciseId);
        Exercise exercise = new Exercise(id, taskId, false, Integer.MAX_VALUE, 3 * 60);
        Random random = new Random();
        int diagonal = random.nextInt(20) + 1;
        double area = PI * diagonal * diagonal / 4;
        String answer = new DecimalFormat("#.0").format(area);
        exercise.addParam("diameter", diagonal);
        exercise.addParam("selected", false);
        exercise.addParam("type", "D-A");
        exercise.setAnswer(answer);
        return exercise;
    }

    /**
     * Generates exercises for Bonus1.
     */
    private List<Exercise> initBonus1(String taskId) {
        List<Exercise> exercises = new ArrayList<>();
        String lastKey = exerciseDAO.getLastKey();
        if (lastKey == null) {
            lastKey = "0";
        }
        int id = Integer.parseInt(lastKey);

        exercises.add(bonus1(++id, taskId, 0, "310", "cm"));
        exercises.add(bonus1(++id, taskId, 1, "598", "cm"));
        exercises.add(bonus1(++id, taskId, 2, "288", "m"));
        exercises.add(bonus1(++id, taskId, 3, "18", "m"));
        exercises.add(bonus1(++id, taskId, 4, "3456", "cm"));
        exercises.add(bonus1(++id, taskId, 5, "174", "m"));
        return exercises;
    }

    /**
     * Helper method to create Bonus1 exercises.
     */
    private Exercise bonus1(Integer exerciseId, String taskId, int no, String answer, String unit) {
        Exercise exercise = new Exercise(String.valueOf(exerciseId), taskId, true, Integer.MAX_VALUE, 3 * 60, answer);
        exercise.setTime(3 * 60);
        exercise.addParam("no", no);
        exercise.addParam("unit", unit);
        exercise.addParam("selected", false);
        return exercise;
    }

    /**
     * Generates exercises for Bonus2.
     */
    private List<Exercise> initBonus2(String taskId) {
        List<Exercise> exercises = new ArrayList<>();
        String lastKey = exerciseDAO.getLastKey();
        if (lastKey == null) {
            lastKey = "0";
        }
        int id = Integer.parseInt(lastKey);
        exercises.add(bonus2(++id, taskId, 0, 90, 8, "cm"));
        exercises.add(bonus2(++id, taskId, 1, 130, 18, "ft"));
        exercises.add(bonus2(++id, taskId, 2, 60, 19, "cm"));
        exercises.add(bonus2(++id, taskId, 3, 110, 22, "ft"));
        exercises.add(bonus2(++id, taskId, 4, 100, 3.5, "cm"));
        exercises.add(bonus2(++id, taskId, 5, 270, 8, "in"));
        exercises.add(bonus2(++id, taskId, 6, 280, 12, "yd"));
        exercises.add(bonus2(++id, taskId, 7, 250, 15, "mm"));
        return exercises;
    }

    /**
     * Helper method to create Bonus2 exercises.
     */
    private Exercise bonus2(Integer exerciseId, String taskId, int no, int angel, double r, String unit) {
        Exercise exercise = new Exercise(String.valueOf(exerciseId), taskId, true, Integer.MAX_VALUE, 3 * 60);
        exercise.setTime(3 * 60);
        String radius = r + " " + unit;
        exercise.addParam("radius", radius);
        exercise.addParam("no", no);
        exercise.addParam("selected", false);
        double answer = PI * r * r * angel / 360;
        exercise.setAnswer(String.format("%.1f", answer));
        return exercise;
    }

    /**
     * Saves a list of exercises to the database.
     */
    private void saveExercises(List<Exercise> exercises) {
        exercises.forEach(exercise -> exerciseDAO.updateOrSave(exercise.getId(), exercise));
    }

    /**
     * Saves a single exercise to the database.
     */
    public void saveExercise(Exercise exercise) {
        exerciseDAO.updateOrSave(exercise.getId(), exercise);
    }
}
